"""
Copyright 2009, Thomas Dejanovic, Jay Shurtz.
 
This is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of
the License, or (at your option) any later version.

This software is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this software; if not, write to the Free
Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
02110-1301 USA, or see the FSF site: http://www.fsf.org.

User interface to the physical implementation of a hatched design, 
such as an FPGA using a serial port.

A HatchFacade allows the user to read & write registers (in an ASIC, FPGA, etc) 
directly by name from the python console.
Register information must be passed into the facade as an XML file.
The interface to the device (serial, usb, etc) must also be passed in.

HatchFacade instances encapsulate the XML information with the __getattr__()
method so that syntax like: "facade.spartan3_LEDs = 0xFF" is possible.
See 'http://docs.python.org/reference/datamodel.html' for more info.

hatch_facade.py should be used indirectly via the hatch_interface.py module.
"""

id = "$Id: hatch_facade.py 725 2010-09-22 07:34:24Z jayshurtz $"
# $URL: http://hatch.googlecode.com/svn/trunk/hatch_interface/hatch_facade.py $
# $Author: jayshurtz $
version = " ".join(id.split()[1:3])


#Notes on struct:
#    Character 	Byte order 	Size and alignment
#    @ 	native 	native
#    = 	native 	standard
#    < 	little-endian 	standard
#    > 	big-endian 	standard
#    ! 	network (= big-endian) 	standard
#
#pack( 	fmt, v1, v2, ...)
#    Return a string containing the values v1, v2, ... packed according to the 
#    given format. The arguments must match the values required by the format exactly. 
#pack_into( 	fmt, buffer, offset, v1, v2, ...)
#    Pack the values v1, v2, ... according to the given format, write the packed
#    bytes into the writable buffer starting at offset. Note that the offset is
#    not an optional argument.
#    New in version 2.5. 
#unpack( 	fmt, string)
#    Unpack the string (presumably packed by pack(fmt, ...)) according to the 
#    given format. The result is a tuple even if it contains exactly one item.
#    The string must contain exactly the amount of data required by the format 
#    (len(string) must equal calcsize(fmt)). 
#unpack_from( 	fmt, buffer[,offset = 0])
#    Unpack the buffer according to tthe given format. The result is a tuple
#    even if it contains exactly one item. The buffer must contain at least the
#    amount of data required by the format (len(buffer[offset:]) must be at least calcsize(fmt)).
#    New in version 2.5. 
#calcsize( 	fmt)
#    Return the size of the struct (and hence of the string) corresponding to the given format. 
#
#x      pad byte  	no value  	
#c 	char 	        string of length 1 	
#b 	signed char 	integer 	
#B 	unsigned char 	integer 	
#h 	short 	        integer 	
#H 	unsigned short 	integer 	
#i 	int 	        integer 	
#I 	unsigned int 	long 	
#l 	long 	        integer 	
#L 	unsigned long 	long 	
#q 	long long 	long 	(1)
#Q 	unsigned long long  long    (1)
#f 	float 	        float 	
#d 	double 	        float 	
#s 	char[] 	        string 	
#p 	char[] 	        string 	
#P 	void * 	        integer 

import struct   # Strings as packed binary data.


import to_hatch_structure
import hatch_easy_logger
import hatch_xml_methods
import hatch_iterators
from hatch_constants import BASE_ADDRESS, NAME_ATTRIBUTE
from hatch_constants import PARENT_ATTRIBUTE, PROPERTIES_ATTRIBUTE, CHILDNODES_ATTRIBUTE
from hatch_constants import HATCH_INTERFACE_ATTRIBUTES, ILLEGAL_NAMES, INVISIBLE
from hatch_constants import BASE_BIT, BIT_WIDTH, PACKED#, AUX_WRITE_COMMAND, AUX_READ_COMMAND


ERROR_STRUCT = 37


# Define the required values that a hatch node must define before it can be added to the facade class.
REQUIRED_VALUES = set([NAME_ATTRIBUTE, BASE_ADDRESS])
#IGNORED_VALUES = set([])    # Values to ignore when adding the hatch node to the facade class.


# TODO Move this typedef into hatch_constants.py ?  A dictionary is expected by facade, interface, and serial.
# XXX HatchNode attributes and properties cannot have the same names, so it is ok to throw them all in the same dictionary here.
HatchFacadeNode = dict


# Hatch facade factory class.
class HatchFacadeFactory(hatch_easy_logger.EasyLogger):
    """ Class for returning various types of hatch facades.
        As it turns out, it's impossible to tab-complete nested dictionary keys with "."
        AND use the containing HatchFacade __getattr__ function with a dictionary key.  
        Otherwise, implementing the document-object-model as a plain nested dictionary
        would be a perfect approach.
    """
    def __init__(self, loggerName=None, logHandlers=[]):    # XXX Pass in unique name ?
        """ """
        hatch_easy_logger.EasyLogger.__init__(self, loggerName, logHandlers)    # Create self.logger.

    def create(self, hatchFacade, xmlFile, nodeType):
        """ Return the appropriate hatch facade type based on args.
            'xmlFile' is the hatched .xml file containing the hatched implementation data.
        """
        self.dom = hatch_xml_methods.xml.dom.minidom.parse(xmlFile) # Get doc from XML file.
        dom = hatch_xml_methods.next_xml_hatch_node_tag(self.dom)   # Get top node in design.
        #if nodeType == HatchFacadeNode:
        #    self.logger.info("Implementing basic facade.")
        #    self.implement_facade_basic(hatchFacade, dom, "", HatchFacadeNode())
        #else:
        self.logger.info("Implementing nested facade.")
        self.implement_facade_nested(hatchFacade, dom, None, nodeType)

    if False:
        def implement_facade_basic(self, hatchFacade, xmlNode, parentName, parentValues):   # Recursive.
            """ Implements the document-object-model for a basic hatch facade.
                This is a flat facade, so global names are used and the invisible property is ignored.
            """
            name, values = self.xml_node_to_HatchFacadeNode(xmlNode)
            if xmlNode.parentNode.nodeType != self.dom.DOCUMENT_NODE:   # Flat facade: Delimit name if not a top level tag.
                name = parentName + hatchFacade.delimiter + name
            values[CHILDNODES_ATTRIBUTE] = [] # This is ok as childNodes is a forbidden HatchNode property name.
            try:
                parentValues[CHILDNODES_ATTRIBUTE].append(name) # Add name as childNode property to name's parent.
            except KeyError, e:
                pass    # Parent node doesn't have/need a childNodes property.
            for childNode in hatch_xml_methods.get_xml_hatch_child_nodes(xmlNode):
                self.implement_facade_basic(hatchFacade, childNode, name, values)  # Recurse with this name & values pair as parentName & parentProperties.
            hatchFacade.nodesMap[name] = HatchFacadeNode(values) # Add node to facade nodesMap, TODO pass in the HatchFacadeNode class ?
            self.logger.debug('Added: ' + name + ", " + repr(values))

    def implement_facade_nested(self, hatchFacade, xmlNode, parent, nodeType):
        """ Implements the document-object-model as a nested structure for syntax like 'a.b.c.d = 6'.
        """
        name, values = self.xml_node_to_HatchFacadeNode(xmlNode)
        values[CHILDNODES_ATTRIBUTE] = [] # XXX This is ok as childNodes is a forbidden HatchNode property name.
        thisNode = nodeType(hatchFacade, name, parent, values) # TODO pass in the HatchFacadeNodeNested class ?
        if parent is None:  # The top node must be added to the hatchFacade object.
            object.__setattr__(hatchFacade, 'topNode', thisNode)
            for childNode in hatch_xml_methods.get_xml_hatch_child_nodes(xmlNode):
                self.implement_facade_nested(hatchFacade, childNode, thisNode, nodeType)   # XXX Recurse with this node as parent.
            self.logger.debug('Added: ' + name + ", " + repr(values))
        elif not values.get(INVISIBLE, False):
            # Add self to parent childNodes.
            object.__getattribute__(parent, CHILDNODES_ATTRIBUTE).append(thisNode)
            object.__getattribute__(parent, PROPERTIES_ATTRIBUTE)[CHILDNODES_ATTRIBUTE].append(str(thisNode))
            for childNode in hatch_xml_methods.get_xml_hatch_child_nodes(xmlNode):
                self.implement_facade_nested(hatchFacade, childNode, thisNode, nodeType)   # XXX Recurse with this node as parent.
            self.logger.debug('Added: ' + name + ", " + repr(values))
        else:   # Node is invisible.
            for childNode in hatch_xml_methods.get_xml_hatch_child_nodes(xmlNode):
                self.implement_facade_nested(hatchFacade, childNode, parent, nodeType)   # XXX Recurse with parent as parent.
            self.logger.debug('Skipped: ' + name + ", " + repr(values))

    def xml_node_to_HatchFacadeNode(self, xmlNode):  # Not recursive.
        """ If xml node is a valid hatch node, return a name and a HatchFacadeNode 
            containing properties & attributes (which are not allowed to overlap.)
            Error if input is not a valid xml hatch node.
            return name & values, for example: ('led', {'address':0xfa0e, 'hatch_leaf':True, ...})   
        """
        node_xml = hatch_xml_methods.get_node_xml(xmlNode)
        attributes, properties = hatch_xml_methods.get_xml_hatch_node_details(xmlNode) # Get hatchNode properties & attributes.
        name = attributes.get(NAME_ATTRIBUTE, properties.get(NAME_ATTRIBUTE, "")).strip()
        #self.logger.debug('name: ' + repr(name))
        #self.logger.debug('attributes: ' + repr(attributes))
        #self.logger.debug('properties: ' + repr(properties))
        if name in ILLEGAL_NAMES:   # Child named 'read()' wouldn't be accessable as custom __getattr__() would not be called.
            raise AssertionError, "Hatched XML nodes must have a valid '" + NAME_ATTRIBUTE + "' attribute or property: " + node_xml
        if len((REQUIRED_VALUES - set(attributes.keys())) - set(properties.keys())) != 0:
            raise AssertionError, 'Node does not have required attributes and/or properties: ' + node_xml
        if len(set(attributes.keys()) & set(properties.keys())) != 0:
            raise AssertionError, 'Node attributes and properties should not have the same names: ' + node_xml
        # XXX Drop IGNORED_VALUES to keep dict small ?
        values = HatchFacadeNode()
        values.update(properties)
        values.update(attributes)   # Safe, already checked that no keys overlap with attributes dict.
        return (name, values)


# Hatch Facade classes & base classes.
# XXX Please note: HatchFacade classes do no logging! They are only a thin layer 
# that provides user flexibility when typing commands to be sent down the 
# interface to the hatched design. As such, the INTERFACE object may log
# during reads and writes, but the facade merely automatically calls 
# read & write in the correct way for the user when they try to access a 
# register in the design.
class ImmutableObject(object):
    """ Class for an object with immutable attributes.
    """
    IMMUTABLE_ATTRIBUTES_MESSAGE = "Cannot alter class attributes"

    def __setattr__(self, attr, value):
        """ """
        raise AttributeError, ImmutableObject.IMMUTABLE_ATTRIBUTES_MESSAGE

    def __delattr__(self, attr):
        """ """
        raise AttributeError, ImmutableObject.IMMUTABLE_ATTRIBUTES_MESSAGE


# TODO - Use any auto-increment read/write opcodes ? (length, startAddress, data data data data...)
class HatchFacade(ImmutableObject):
    """ Base class for all hatch facades.
        Encapsulates the interface to the hatched implementation.
        Just the universal stuff goes here, most facades will have their
        own style of interface & usage syntax.
        Defines things like 'read' and 'write' opcodes, byteOrder & format, and 
        opcode, address, and data length for different implementations.
        Note that the data width has to be as big as or bigger than the biggest 
        register on the device, or this class will *NOT* read or write the 
        entire register contents.  
    """
    def __init__(self, opCodes={'read':0x21,'write':0x22}, byteOrder='>', \
        format='B', opCodeLen=1, addressLen=1, dataLen=1):
        """ 'opcodes' is a dictionary defining operation codes by name.
            It must include the opcodes used to read & write registers in the 
            design. As a dict, it should be easy to extend when inheriting.
            'byteOrder' is the byte order character used by the struct module 
            for packing/unpacking serial data (default big endian.)
            'format' is the format character used by the struct module for 
            packing/unpacking serial data (default 1 unsigned char from int.)
            'opCodeLen' is the opcodes' length in bytes.
            'addressLen' is the register address' length in bytes.
            'dataLen' is the data bus width in bytes.  All data values 
            read/written to the serial port will be of this width.
        """
        object.__setattr__(self, 'opCodes', opCodes)
        object.__setattr__(self, 'byteOrder', byteOrder)
        object.__setattr__(self, 'format', format)
        object.__setattr__(self, 'formatSize', struct.calcsize(self.format))    # Format length in bytes.
        object.__setattr__(self, 'opCodeLen', opCodeLen)
        object.__setattr__(self, 'addressLen', addressLen)
        object.__setattr__(self, 'dataLen', dataLen)
        publicAttributes = [self.pack.__name__+'(', self.unpack.__name__+'(']
        publicAttributes += [self.put.__name__+'(', self.get.__name__+'(']
        publicAttributes += [self.get_bit_mask.__name__+'(', self.mask.__name__+'(', self.unmask.__name__+'(']
        publicAttributes += [self.write_register.__name__+'(', self.read_register.__name__+'(']
        publicAttributes += [self.write.__name__+'(', self.read.__name__+'(']
        publicAttributes += ['opCodes', 'byteOrder', 'format', 'formatSize']
        publicAttributes += ['opCodeLen', 'addressLen', 'dataLen']
        object.__setattr__(self, 'publicAttributes', publicAttributes)

    def __dir__(self):
        """ For custom tab completion.
            If the output of this function looks wrong, be aware that if any 
            errors occour here they are ignored and the default dir() function 
            is used instead.
        """
        return self.publicAttributes

    def pack(self, data, length):
        """ Pack everything in the same format using an integer to specify length.
            'data' is the data to pack.
            'length' is the data length in bytes. Shorter data is padded.
            Ex: >>> struct.pack('>BBBB', (0x00ABCDEF >> 24) & 0xFF, \\
                    (0x00ABCDEF >> 16) & 0xFF, (0x00ABCDEF >> 8) & 0xFF, \\
                    (0x00ABCDEF >> 0) & 0xFF)
                '\x00\xab\xcd\xef'
        """
        try:
            lengthInFormat = length / self.formatSize   # Length in bytes / format length in bytes.
            fullBits = (1 << (self.formatSize * 8)) - 1 # ex: (1 << 8) - 1 = 0xFF
            dataShifted = [(data >> bitShift) & fullBits for bitShift in range(0, lengthInFormat*8, 8)]   # Shift & keep significant part only.
            dataShifted.reverse()
            packFormat = self.byteOrder+self.format*lengthInFormat
            #self.logger.debug("pack format & data: " + packFormat + ", " + str(dataShifted) + " " + ", ".join([hex(i) for i in dataShifted]))
            retVal = struct.pack(packFormat, *dataShifted)
        except struct.error, e: # Raise a more generic exception.
            raise IOError, (ERROR_STRUCT, e)
        return retVal

    def unpack(self, data, length):
        """ Inverse of pack.
            'data' is the packed data string.
            'length' is the unpacked data length in bytes.
        """
        try:
            lengthInFormat = length / self.formatSize    # Length in bits / format length in bytes. 
            packFormat = self.byteOrder+self.format*lengthInFormat
            dataShifted = struct.unpack(packFormat, data)
            #self.logger.debug("unpack format & data: " + packFormat + ", " + str(dataShifted) + " " + ", ".join([hex(i) for i in dataShifted]))
            retVal = 0
            for val in dataShifted:
                retVal = retVal << 8
                retVal |= val
        except struct.error, e: # Raise a more generic exception.
            raise IOError, (ERROR_STRUCT, e)  # Incorrect data or length.
        return retVal

    # TODO Error & do not put a value if there is a value to get() ?
    def put(self, value, *args):
        """ Write (or put) raw values to interface.

            PLEASE NOTE:  This function assumes that people think in "bytes" 
            when "putting data" on the interface, so ALL parameters are forced 
            to be 1 byte long ie: 
                '0x1001' is treated as '0x1'
                '0x10, 0x01' is treated as '0x10, 0x01'
                
            'value' is the integer value to write.
            '*args' is the list of any further integer values to write.
            A single value, values, list, or argument list may be passed in as so:
                put(1)  or
                put(1,2,3,4)   or    
                put([1,2,3,4])  or
                put(*[1,2,3,4])
        """
        try:
            values = [value[0]] + value[1:] #+ list(args)  # Support lists.
        except TypeError, e:
            values = [value] + list(args)   # Support multiple values.
        # XXX Put all values as a single value instead of calling the write() 
        # XXX function multiple times.  This is a workaround for the slightly
        # XXX longer function-call-overhead-delay in Windows (which breaks Byron OTP writes.)
        if len(values) > 0:
            putValue = 0
            for v in values:
                putValue <<= 8  # Shift by 1 byte and OR.
                putValue |= v
            self.portWrapper.flushInput()   # Clear data from before this put.
            self.portWrapper.write(self.pack(putValue, len(values)))

    def get(self, length=None):
        """ Read raw bytes from the port.
            'length' is the number of expected units of the format specified in init.
            Do not specify a value to read all values waiting on the interface.
            Specify a length of 0 to read no values, otherwise this function
            will try to read *at least* one value to force a timeout.
            Returns the read values.
        """
        retVal = ""
        # If length is unknown, try to read *at least* 1 byte to force a timeout.
        # Reading 0 bytes will exit serial.read() prematurely without error.  
        if length is None:
            length = max(self.portWrapper.inWaiting(), 1)
        retVal = self.portWrapper.read(length)  # Reading 0 bytes will exit the function prematurely.
        try:
            retVal = self.unpack(retVal, max(len(retVal), 1))   # Try to unpack at least 1 byte, or error.
        except IOError, e:
            raise IOError, "Read timeout " + repr(retVal)
        return retVal

    def get_bit_mask(self, nodeDict):
        """ Get the specific register bits of interest.
            'nodeDict' is a dictionary (or object indexed by key) with the address/bits we wish to access.
            Returns the node base bit and mask value (ex: 00011100 or 0x1C).
        """
        baseBit = nodeDict[BASE_BIT]
        bitWidth = nodeDict[BIT_WIDTH]
        maskValue = (1 << (baseBit + bitWidth)) - (1 << baseBit)   
        return baseBit, maskValue

    def unmask(self, baseBit, maskValue, currentValue):
        """ Return current value after masking & shifting the relevant register bits.
        """
        return (currentValue & maskValue) >> baseBit

    def mask(self, baseBit, maskValue, currentValue, value):
        """ Return a Read-modify-write value for writing to specific bits while leaving others alone.
            'value' is the value to write to the bits of interest.
        """
        currentValue = (currentValue - (currentValue & maskValue))  # Clear bits of interest.
        value = ((value << baseBit) & maskValue)   # Shift data up to base bit of register & mask.
        value |= currentValue
        return value

    # TODO - specify a protocol class, such as FixedLength (similiar to SLIP, etc.) ?
    def read_register(self, nodeDict):
        """ Read a register.
            'nodeDict' is a dictionary (or object indexed by key) with the address we wish to read.
            Returns the current value in that address.
        """
        retVal = ""
        self.portWrapper.flushInput()  # Flush any values before 'read' command is sent.
        opcode = self.opCodes['read']   # XXX
        val = self.pack(opcode, self.opCodeLen)
        val += self.pack(nodeDict[BASE_ADDRESS], self.addressLen)
        self.portWrapper.write(val)
        retVal = self.portWrapper.read(self.dataLen)  # Read all bytes or timeout if a timeout is set.
        try:
            retVal = self.unpack(retVal, self.dataLen)
        except IOError, e:
            raise IOError, "Read timeout " + repr(retVal)
        return retVal

    def write_register(self, nodeDict, value):
        """ Write a register.
            'nodeDict' is a dictionary (or object indexed by key) with the address we wish to write.
            'value' is the value to write to that address.
        """
        opcode = self.opCodes['write']  # XXX
        val = self.pack(opcode, self.opCodeLen)
        val += self.pack(nodeDict[BASE_ADDRESS], self.addressLen)
        val += self.pack(value, self.dataLen)
        self.portWrapper.write(val)

    def read(self, nodeDict):
        """ Read a node in the hatched design.
            'nodeDict' is a dictionary (or object indexed by key) with the address/bits we wish to read.
            Returns the current value of the node.
        """
        retVal = ""
        retVal = self.read_register(nodeDict)
        if nodeDict.get(PACKED, False): # XXX Add logic around BASE_BIT, BIT_WIDTH ?
            baseBit, maskValue = self.get_bit_mask(nodeDict)
            retVal = self.unmask(baseBit, maskValue, retVal)
        return retVal

    def write(self, nodeDict, value):
        """ Write a node in the hatched design.
            The value written to a register may not always match the value that is read back from that register.
            Different registers may not be writable, may automatically increment or clear, and so on.
            As such, this write is NOT followed by a read to check that the write was successful.
            Checking is usually not necessary, and should be done manually by the user.
            'nodeDict' is a dictionary (or object indexed by key) with the address/bits we wish to write.
            'value' is the value to write to the node. (This should be a hex integer.)
        """
        if nodeDict.get(PACKED, False): # If node is packed.
            baseBit, maskValue = self.get_bit_mask(nodeDict)
            currentValue = self.read_register(nodeDict)
            value = self.mask(baseBit, maskValue, currentValue, value)
        self.write_register(nodeDict, value)


if False:
    class HatchFacadeBasic(HatchFacade):
        """ These instances are flat and override the get/set attribute functions 
            for syntax like 'a.b_c_d = 6'.  This class cannot support brackets or 
            custom node functions such as 'print a.b_c[0]' or 'a.b_c_d.info()'.
        """
        def __init__(self, portWrapper, xmlFile, hatchFacadeFactory, delimiter="_"):
            """ 'portWrapper' is the interface to the [physical] device using that implementation. 
                'xmlFile' is the hatched XML file that holds the design.
                'hatchFacadeFactory' is the facade factory instance used to build the facade (or hatch nodes map.)
                'delimiter' is the character used to delimit names of 'nested' hatch nodes (as the facade is flat.)
            """
            object.__setattr__(self, 'portWrapper', portWrapper)
            object.__setattr__(self, 'nodesMap', {})    # Dictionary of all hatch facade nodes by name.
            object.__setattr__(self, 'delimiter', delimiter)
            hatchFacadeFactory.create(self, xmlFile, HatchFacadeNode)
            publicAttributes = [self.put.__name__, self.get.__name__]

            #publicAttributes = [self.put.__name__+'(', self.get.__name__+'(']
            #publicAttributes += [self.get_bit_mask.__name__+'(', self.mask.__name__+'(', self.unmask.__name__+'(']
            #publicAttributes += [self.write_register.__name__+'(', self.read_register.__name__+'(']
            #publicAttributes += [self.write.__name__+'(', self.read.__name__+'(']
            
            publicAttributes += [self.read.__name__, self.write.__name__]
            publicAttributes += ['portWrapper', 'nodesMap']
            publicAttributes += self.nodesMap.keys()    # Must come after factory call which populates nodesMap.
            object.__setattr__(self, 'publicAttributes', publicAttributes)

        def __getattr__(self, attr):
            """ Called only when __getattribute__() fails.
                Gets a hatch node from the nodes map and then does an interface read.
                This supports syntax like 'print a.b_c_d'
            """
            retVal = None
            try:
                retVal = object.__getattribute__(self, 'nodesMap')[attr]
            except KeyError, e:
                raise AttributeError, e
            retVal = object.__getattribute__(self, 'read')(retVal)
            return retVal         

        def __setattr__(self, attr, value):
            """ Set a single value or list of values depending on the type of 
                hatch node to set.  Block-type nodes with 1 or more child nodes 
                are written breadth first (as opposed to depth first.)
                This supports syntax like 'a.b_c_d = 6' or 'a.b_c = [6,7,8]'
                or 'a.b_c = 6,7,8 '.
            """
            try:
                retVal = self.nodesMap[attr]
            except KeyError, e:
                raise AttributeError, e # Handle internal nodesMap dictionary like an object.
            try:
                self.write(retVal, value[0], *value[1:])    # Support multiple values.
            except TypeError, e:
                self.write(retVal, value)

        def read(self, hatchNode):
            """ Do an interface read.
                'hatchNode' is the hatch node from the nodes map to read.
                Returns a single value or list of values depending on the type of 
                hatch node.  Block-type nodes with 1 or more child nodes 
                are always read breadth first (as opposed to depth first.)
            """
            # TODO Add switch for breadth/depth first ?
            retVal = None
            if len(hatchNode[CHILDNODES_ATTRIBUTE]) == 0: 
                retVal = self.portWrapper.read(hatchNode)
            else:   # Recursive reads of all child nodes will be necessary.     
                retVal = []
                breadthFirstQueue = [hatchNode]
                while len(breadthFirstQueue) > 0:
                    queuedNode = breadthFirstQueue.pop(0)
                    breadthFirstQueue += [self.nodesMap[c] for c in queuedNode[CHILDNODES_ATTRIBUTE]]
                    if len(queuedNode[CHILDNODES_ATTRIBUTE]) == 0: 
                        retVal.append(self.portWrapper.read(queuedNode))
            return retVal 

        def write(self, hatchNode, value, *args):
            """ Do an interface write.
                'hatchNode' is the hatch node from the nodes map to write.
                'value' is the integer value to write.
                '*args' is the list of any further integer values to write.
                Writes a single value or list of values depending on the type of 
                hatch node to write.  Block-type nodes with 1 or more child nodes 
                are always written breadth first (as opposed to depth first.)
            """
            if len(hatchNode[CHILDNODES_ATTRIBUTE]) == 0: 
                self.portWrapper.write(hatchNode, value)
            else:   # Recursive reads of all child nodes will be necessary.
                try:
                    values = [value[0]] + value[1:] #+ list(args)  # Support lists.
                except TypeError, e:
                    values = [value] + list(args)   # Support multiple values.
                breadthFirstQueue = [hatchNode]
                while len(breadthFirstQueue) > 0 and len(values) > 0:
                    queuedNode = breadthFirstQueue.pop(0)
                    breadthFirstQueue += [self.nodesMap[c] for c in queuedNode[CHILDNODES_ATTRIBUTE]]
                    if len(queuedNode[CHILDNODES_ATTRIBUTE]) == 0:
                        val = values.pop(0)
                        # TODO Print a nicer error when 'value' is not a list? 
                        # Require a list entry for each child node?
                        self.portWrapper.write(queuedNode, val)


class HatchFacadeNodeNested(ImmutableObject):
    """ Hatch node as represented by a hatch facade.
        (This class replaces the simple dictionaries used to represent hatch 
        nodes in the basic hatch facade class.)
        Rather than using a simple dictionary of properties, this class exists
        to also define some custom behaviours.  As such it needs to point back
        to its own facade.  
        With this class, syntax such as 'a.b.c.d(6)' & 'a.b.c[0](6)' is possible.
    """
    ITERATE_MESSAGE = "This node has child nodes, use depth-first or breadth-first iterator"

    class IteratorDepthFirst(hatch_iterators.HatchNodeIteratorDepthFirst):
        """ Depth first iterator for HatchFacadeNodeNested class.
        """
        pass

    class IteratorBreadthFirst(hatch_iterators.HatchNodeIteratorBreadthFirst):
        """ Breadth first iterator for HatchFacadeNodeNested class.
        """
        pass

    def __init__(self, hatchFacade, name, parent, properties):
        """ 'hatchFacade' is the hatch facade instance that holds this node in it's nodes map.   
        """
        object.__setattr__(self, 'hatchFacade', hatchFacade)
        object.__setattr__(self, NAME_ATTRIBUTE, name)
        object.__setattr__(self, PARENT_ATTRIBUTE, parent)
        object.__setattr__(self, PROPERTIES_ATTRIBUTE, properties)  # Unlike HatchNode class, this includes things like 'name'.
        object.__setattr__(self, CHILDNODES_ATTRIBUTE, [])  # Actual child node objects are held here in a nested fashion.
        publicAttributes = ['hatchFacade', NAME_ATTRIBUTE, PROPERTIES_ATTRIBUTE, PARENT_ATTRIBUTE, CHILDNODES_ATTRIBUTE]
        publicAttributes += [self.read.__name__+'()', self.write.__name__+'(']
        publicAttributes += [self.depthFirst.__name__+'()', self.breadthFirst.__name__+'()']
        if False:
            publicAttributes += [self.readDepthFirst.__name__+'()', self.writeDepthFirst.__name__+'(']
            publicAttributes += [self.readBreadthFirst.__name__+'()', self.writeBreadthFirst.__name__+'(']
        object.__setattr__(self, 'publicAttributes', publicAttributes)

    def __str__(self):
        """ """
        return self.name

    def __dir__(self):
        """ For custom tab completion.
            If the output of this function looks wrong, be aware that if any 
            errors occour here they are ignored and the default dir() function 
            is used instead.
        """
        return self.publicAttributes + self.properties[CHILDNODES_ATTRIBUTE]    # Include child node names in tab-complete suggestions.

    def depthFirst(self):
        """ Return a depth-first iterator for this hatch facade node.
        """
        return HatchFacadeNodeNested.IteratorDepthFirst(self)

    def breadthFirst(self):
        """ Return a breadth-first iterator for this hatch facade node.
        """
        return HatchFacadeNodeNested.IteratorBreadthFirst(self)

    def read(self):
        """ Use hatch facade that contains this object to do an interface read.
            all interfaces expect a dictionary (or object indexed by key) 
            with the address we wish to read.
            Any node that has no child nodes (a leaf), or has only child nodes with the 
            same base address (a pack) may be read non-iteratively with this function.
        """
        retVal = None
        if False in [self.properties[BASE_ADDRESS] == node.properties[BASE_ADDRESS] for node in self.childNodes]:
            raise AssertionError, HatchFacadeNodeNested.ITERATE_MESSAGE
        else:
            retVal = self.hatchFacade.read(self.properties)
        return retVal

    def write(self, value):
        """ Use hatch facade that contains this object to do an interface write.
            all interfaces expect a dictionary (or object indexed by key) 
            with the address we wish to write.
            Any node that has no child nodes (a leaf), or has only child nodes with the 
            same base address (a pack) may be written non-iteratively with this function.
        """
        if False in [self.properties[BASE_ADDRESS] == node.properties[BASE_ADDRESS] for node in self.childNodes]:
            raise AssertionError, HatchFacadeNodeNested.ITERATE_MESSAGE
        else:
            self.hatchFacade.write(self.properties, value)

    if False:
        def readIterative(self, iterator):
            """ Read self & child nodes in a order specified by the iterator.
                Note that only leaf nodes are read, block nodes are iterated through.
            """
            retVal = []
            for node in iterator:
                if len(node.childNodes) == 0:
                    retVal.append(self.hatchFacade.read(node.properties))
            return retVal 

        def writeIterative(self, iterator, value, *args):
            """ Write self & child nodes in a order specified by the iterator.
                Note that only leaf nodes are written, block nodes are iterated through.
            """
            try:
                values = [value[0]] + value[1:] #+ list(args)  # Support lists.
            except TypeError, e:
                values = [value] + list(args)   # Support multiple values.
            done = False
            node = iterator.next()
            while not done and len(values) > 0:
                if len(node.childNodes) == 0:
                    self.hatchFacade.write(node.properties, values.pop(0))
                try:
                    node = iterator.next()
                except StopIteration:
                    done = True

        def readDepthFirst(self):
            """ Read self & child nodes in depth-first order.
                Note that only leaf nodes are read, block nodes are iterated through.
            """
            return self.readIterative(self.depthFirst())

        def writeDepthFirst(self, value, *args):
            """ Write self & child nodes in depth-first order.
                Note that only leaf nodes are written, block nodes are iterated through.
            """
            self.writeIterative(self.depthFirst(), value, *args)

        def readBreadthFirst(self):
            """ Read self & child nodes in breadth-first order.
                Note that only leaf nodes are read, block nodes are iterated through.
            """
            return self.readIterative(self.breadthFirst())

        def writeBreadthFirst(self, value, *args):
            """ Write self & child nodes in breadth-first order.
                Note that only leaf nodes are written, block nodes are iterated through.
            """
            self.writeIterative(self.breadthFirst(), value, *args)

    def __getattr__(self, attr):
        """ Get child nodes as attributes to support syntax like 'print a.b.c.d'
            Interface reads & writes are handled by the returned object itself.
        """
        retVal = None
        try:
            retVal = object.__getattribute__(self, PROPERTIES_ATTRIBUTE)[CHILDNODES_ATTRIBUTE].index(attr)   # Get the list index by name.
        except ValueError, e:
            raise AttributeError, repr(attr) + " does not exist."   # Treat child nodes as individual attributes.
        try:
            retVal = object.__getattribute__(self, CHILDNODES_ATTRIBUTE)[retVal]  # Get the object by list index.
        except KeyError, e:
            raise AttributeError, repr(attr) + " does not exist."   # Treat child nodes as individual attributes.
        return retVal

    def __call__(self, *args):  # Do not support **keyargs.
        """ Emulate a callable object.
            Use __call__ as shorthand for self.read(), self.write(), etc.
            Support syntax like 'print a.b_c_d()' to read, 'a.b_c(6, 7, 8)' to write.
            If the argument list is empty () a read will be performed.
            If the argument list is not empty, a write will be performed.
        """
        retVal = None
        if len(args) == 0:
            retVal = self.read()
        else:
            self.write(*args)
        return retVal

    # Functions for using brackets are here.  
    # Please note that using an iterator is probably the better alternative.
    IMMUTABLE_ITEMS_MESSAGE = "Cannot alter class items"

    def __len__(self):
        """ """
        return len(self.childNodes)

    def __iter__(self):
        """ Iterate through child nodes.  
            Does NOT recursively iterate through child nodes of child nodes.
            For recursive iteration, use a breadth or depth first iterator explicitly.
        """
        return self.childNodes.__iter__()
        
    def __contains__(self, item):
        """ """
        return self.childNodes.__contains__(item)

    def __getitem__(self, key):
        """ Get child node object by name (or failing that, index) within the 
            child nodes list.
            ex: "print a.b[3:5]" or "print a.b['c':'e']".
            A key that is a string is accepted, but be aware that nodes that 
            have names such as '3' may not necessarily be the 3rd child node 
            in the list.
        """
        retVal = None
        try:    # Handle slice objects.
            start, stop, step = key.start, key.stop, key.step
            try:
                start = self.properties[CHILDNODES_ATTRIBUTE].index(str(key.start))   # Convert int/string to string & lookup index.
            except ValueError, e:
                pass    # key not found.
            try:
                stop = self.properties[CHILDNODES_ATTRIBUTE].index(str(key.stop)) + 1   # Don't stop before last item on list if indexed by string (unlike int.)
            except ValueError, e:
                pass    # key not found.
            key = slice(start, stop, step)
        except AttributeError, e:
            pass    # key was not a slice object (such as [0:10:2]).
        try:
            key = self.properties[CHILDNODES_ATTRIBUTE].index(key)  # Lookup index of string.
        except ValueError, e:
            pass    # key not found (or was not string)
        retVal = self.childNodes.__getitem__(key)
        return retVal

    def __setitem__(self, key, value):
        """ """
        raise TypeError, HatchFacadeNodeNested.IMMUTABLE_ITEMS_MESSAGE  # Use AssertionError instead ?

    def __delitem__(self, key):
        """ """
        raise TypeError, HatchFacadeNodeNested.IMMUTABLE_ITEMS_MESSAGE


class HatchFacadeNested(HatchFacade):
    """ Nested Hatch Facade class.
        Encapsulates the interface to the hatched implementation.
        These instances are nested and use a custom node class
        (rather than a simple dictionary) for syntax like 'print a.b.c.d.read()'
    """
    nodeClass = HatchFacadeNodeNested
    
    def __init__(self, portWrapper, xmlFile, hatchFacadeFactory, \
        opCodes={'read':0x21,'write':0x22}, byteOrder='>', format='B', \
        opCodeLen=1, addressLen=1, dataLen=1, delimiter=None):
        """ 'portWrapper' is the interface to the [physical] device using that implementation. 
            'xmlFile' is the hatched XML file that holds the design.
            'hatchFacadeFactory' is the facade factory instance used to build the facade (or hatch nodes map.)
            'delimiter' is ignored.
        """
        HatchFacade.__init__(self, opCodes, byteOrder, format, opCodeLen, addressLen, dataLen)
        object.__setattr__(self, 'portWrapper', portWrapper)
        object.__setattr__(self, 'topNode', None)    # Top node in design.
        hatchFacadeFactory.create(self, xmlFile, self.nodeClass)
        publicAttributes = self.publicAttributes
        publicAttributes += ['portWrapper']
        publicAttributes += [str(self.topNode)] # Must be done after factory call populates topNode.
        object.__setattr__(self, 'publicAttributes', publicAttributes)

    def __getattr__(self, attr):
        """ Called only when __getattribute__() fails.
            Gets the top node by name (as there is only one top node.)  
            Interface reads & writes are handled by the returned object itself.
        """
        retVal = None
        if attr == str(object.__getattribute__(self, 'topNode')):    # attr is top node's name.
            retVal = object.__getattribute__(self, 'topNode')
        return retVal


# Test that the hatch interface attributes are valid.
# This also implicitly tests that the set of illegal hatch node names is
# correct, see hatch_constants.py.
if False:   # XXX For development only.
    class DummyFactory(object):
        def create(self, *args, **keyargs):
            pass
    dummyFactory = DummyFactory()
    hatch_facade_attributes = set() | \
    set(dir(HatchFacadeBasic(None, None, dummyFactory, delimiter="_"))) | \
    set(dir(HatchFacadeNested(None, None, dummyFactory, delimiter=None))) | \
    set(dir(HatchFacadeNodeNested(None, "", None, {})))
    if hatch_facade_attributes != HATCH_INTERFACE_ATTRIBUTES:
        notMatch = "The hatch interface attributes defined in hatch_constants.py do"
        notMatch += " not match the respective class attributes in hatch_facade.py: "
        notMatch += str((hatch_facade_attributes | HATCH_INTERFACE_ATTRIBUTES) - \
            (hatch_facade_attributes & HATCH_INTERFACE_ATTRIBUTES))
        raise AssertionError, notMatch
    del hatch_facade_attributes, dummyFactory, HATCH_INTERFACE_ATTRIBUTES


# Main for testing.
if __name__ == "__main__":

    pass


